/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2016年11月29日
 * <修改描述:>
 */
package com.tx.component.task.interceptor;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.tx.component.task.model.TaskDef;
import com.tx.component.task.model.TaskExecuteLog;
import com.tx.component.task.model.TaskResultEnum;
import com.tx.component.task.model.TaskStatus;
import com.tx.component.task.model.TaskStatusEnum;
import com.tx.component.task.service.TaskDefService;
import com.tx.component.task.service.TaskExecuteLogService;
import com.tx.component.task.service.TaskStatusService;
import com.tx.core.exceptions.SILException;
import com.tx.core.exceptions.util.AssertUtils;

/**
 * 业务层拦截器<br/>
 * <功能详细描述>
 * 
 * @author  Administrator
 * @version  [版本号, 2016年11月29日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class TaskExecuteInterceptor
        implements MethodInterceptor, InitializingBean {
    
    /** 日志记录器 */
    private Logger logger = LoggerFactory
            .getLogger(TaskExecuteInterceptor.class);
    
    /** 方法到任务的映射 */
    private Map<Method, TaskDef> method2taskMap;
    
    private Map<MethodCacheKey, TaskDef> methodCacheKey2taskMap;
    
    /** 签名 */
    private String signature;
    
    /** transactionManager */
    private PlatformTransactionManager transactionManager;
    
    /** 任务定义业务层 */
    private TaskDefService taskDefService;
    
    /** 任务状态业务层 */
    private TaskStatusService taskStatusService;
    
    /** 任务执行日志业务层 */
    private TaskExecuteLogService taskExecuteLogService;
    
    /** 事务句柄 */
    private TransactionTemplate transactionTemplate;
    
    /** 事务句柄 */
    private TransactionTemplate taskStatusTransTemplate;
    
    /** 事务句柄 */
    private TransactionTemplate taskExecuteLogTransTemplate;
    
    /** <默认构造函数> */
    public TaskExecuteInterceptor() {
        super();
    }
    
    /** <默认构造函数> */
    public TaskExecuteInterceptor(Map<Method, TaskDef> method2taskMap,
            String signature, TaskDefService taskDefService,
            TaskStatusService taskStatusService,
            TaskExecuteLogService taskExecuteLogService,
            PlatformTransactionManager transactionManager) {
        super();
        AssertUtils.notEmpty(method2taskMap, "method2taskMap is empty.");
        AssertUtils.notEmpty(signature, "signature is empty.");
        AssertUtils.notNull(taskDefService, "taskDefService is null.");
        AssertUtils.notNull(taskStatusService, "taskStatusService is null.");
        AssertUtils.notNull(taskExecuteLogService,
                "taskExecuteLogService is null.");
        AssertUtils.notNull(transactionManager, "transactionManager is null.");
        
        this.method2taskMap = method2taskMap;
        this.signature = signature;
        this.taskDefService = taskDefService;
        this.taskStatusService = taskStatusService;
        this.taskExecuteLogService = taskExecuteLogService;
        this.transactionManager = transactionManager;
        
        afterPropertiesSet();
    }
    
    /**
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        this.transactionTemplate = new TransactionTemplate(
                this.transactionManager, new DefaultTransactionDefinition(
                        TransactionDefinition.PROPAGATION_REQUIRES_NEW));
        
        this.taskStatusTransTemplate = new TransactionTemplate(
                this.transactionManager, new DefaultTransactionDefinition(
                        TransactionDefinition.PROPAGATION_REQUIRES_NEW));
        this.taskExecuteLogTransTemplate = new TransactionTemplate(
                this.transactionManager, new DefaultTransactionDefinition(
                        TransactionDefinition.PROPAGATION_REQUIRES_NEW));
        
        this.methodCacheKey2taskMap = new HashMap<>();
        this.method2taskMap.entrySet()
                .stream()
                .forEach(
                        e -> this.methodCacheKey2taskMap.put(
                                new MethodCacheKey(e.getKey().getName(),
                                        e.getKey().getParameterTypes()),
                                e.getValue()));
    }
    
    /**
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method m = invocation.getMethod();
        MethodCacheKey cacheKey = new MethodCacheKey(m.getName(),
                m.getParameterTypes());
        if (!this.methodCacheKey2taskMap.containsKey(cacheKey)) {
            //如果是非代理方法，则执行透传执行
            return invocation.proceed();
        }
        
        //如果是具有Task注解的方法:
        final TaskDef finalTask = methodCacheKey2taskMap.get(cacheKey);
        //Object res = null;
        //if (TransactionSynchronizationManager.isSynchronizationActive()) {
        //    res = doInvokeTask(invocation, finalTask);
        //} else {
        //    res = this.transactionTemplate
        //            .execute(new TransactionCallback<Object>() {
        //                @Override
        //                public Object doInTransaction(
        //                        TransactionStatus status) {
        //                    return doInvokeTask(invocation, finalTask);
        //                }
        //            });
        //}
        //新启事务执行，由于mysql的事务隔离性为readonly如果不新启事务执行，如果多次调用会导致幻想读
        //比如连续两次调度，月End任务
        //第一次执行update后，由于还在原来的事务中，会读取到更新前的数据
        Object res = this.transactionTemplate
                .execute(new TransactionCallback<Object>() {
                    @Override
                    public Object doInTransaction(TransactionStatus status) {
                        return doInvokeTask(invocation, finalTask);
                    }
                });
        return res;
    }
    
    /** 
     * 注入执行方法<br/>
     * <功能详细描述>
     * @param invocation
     * @return
     * @throws Throwable [参数说明]
     * 
     * @return Object [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    private Object doInvokeTask(MethodInvocation invocation, TaskDef task) {
        String taskId = task.getId();
        
        //根据任务id添加行级锁
        TaskDef lockTask = this.taskDefService.findWithLockById(taskId);
        AssertUtils.notNull(lockTask, "lockTask is null.");
        
        //查询并锁定任务状态
        Date startDate = new Date();
        Map<String, Object> statusUpdateRowMap = new HashMap<>();
        //考虑到存在setSkip的情况，这里不更新结果，开始时间，结束时间，耗时
        //statusUpdateRowMap.put("start", startDate);
        //statusUpdateRowMap.put("end", null);
        //statusUpdateRowMap.put("consuming", null);
        //statusUpdateRowMap.put("result", TaskResultEnum.UNCOMPLETED);
        //更新任务状态（独立事务）
        TaskStatus taskStatus = updateTaskStatus(taskId,
                TaskStatusEnum.WAIT_EXECUTE,
                TaskStatusEnum.EXECUTING,
                this.signature,
                statusUpdateRowMap);
        
        boolean success = false;
        Object res = null;
        //事务开始执行,如果在开启期间出现异常，则不需要配对的关闭
        TaskExecuteSessionUtils.open(task, taskStatus);
        try {
            //调度处理
            res = invocation.proceed();
            success = true;
        } catch (SILException e) {
            logger.error(e.getErrorMessage());
            
            success = false;
            throw e;
        } catch (Throwable e) {
            logger.error(e.getMessage());
            
            success = false;
            throw new SILException(e.getMessage(), e);
        } finally {
            TaskExecuteSession session = TaskExecuteSessionUtils.close();
            AssertUtils.isTrue(task == session.getTask(), "线程变量中的任务应该为同一对象.");
            AssertUtils.isTrue(taskStatus == session.getTaskStatus(),
                    "线程变量中的任务状态应该为同一对象.");
            
            Date nextFireDate = session.getNextFireDate();
            String taskStatusParameters = TaskExecuteSessionUtils
                    .toParameters(session.getParameters());
            Date endDate = new Date();
            long consuming = endDate.getTime() - startDate.getTime();
            statusUpdateRowMap.put("start", startDate);
            statusUpdateRowMap.put("end", endDate);
            statusUpdateRowMap.put("consuming", consuming);
            if (!session.isSkipped()) {
                statusUpdateRowMap.put("result",
                        success ? TaskResultEnum.SUCCESS : TaskResultEnum.FAIL);//运行时结果
                statusUpdateRowMap.put("executeCount",
                        taskStatus.getExecuteCount() + 1);//执行次数+1
                statusUpdateRowMap.put("parameters", taskStatusParameters);
                statusUpdateRowMap.put("nextFireDate", nextFireDate);
                
                if (success) {
                    statusUpdateRowMap.put("successStart", startDate);
                    statusUpdateRowMap.put("successEnd", endDate);
                    statusUpdateRowMap.put("successConsuming", consuming);
                    statusUpdateRowMap.put("successCount",
                            taskStatus.getSuccessCount() + 1);
                } else {
                    statusUpdateRowMap.put("failStart", startDate);
                    statusUpdateRowMap.put("failEnd", endDate);
                    statusUpdateRowMap.put("failConsuming", consuming);
                    statusUpdateRowMap.put("failCount",
                            taskStatus.getFailCount() + 1);
                }
            } else {
                statusUpdateRowMap.put("result",
                        TaskResultEnum.UNNEED_EXECUTED);//运行时结果
            }
            //更新任务状态
            updateTaskStatus(taskId,
                    TaskStatusEnum.EXECUTING,
                    TaskStatusEnum.WAIT_EXECUTE,
                    null, //将签名更新为空
                    statusUpdateRowMap);
            
            //记录执行日志
            if (!session.isSkipped()) {
                log(lockTask,
                        startDate,
                        endDate,
                        taskStatusParameters,
                        success ? TaskResultEnum.SUCCESS : TaskResultEnum.FAIL,
                        this.signature);
            }
        }
        return res;
    }
    
    /**
     * 独立事务更新任务状态<br/>
     * <功能详细描述>
     * @param taskId
     * @param sourceStatus
     * @param targetStatus
     * @param statusUpdateRowMap [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    private TaskStatus updateTaskStatus(final String taskId,
            final TaskStatusEnum sourceStatus,
            final TaskStatusEnum targetStatus, final String signature,
            final Map<String, Object> statusUpdateRowMap) {
        final Map<String, Object> finalStatusUpdateRowMap = statusUpdateRowMap == null
                ? new HashMap<String, Object>()
                : statusUpdateRowMap;
        final TaskStatusService finalTaskStatusService = this.taskStatusService;
        TaskStatus status = this.taskStatusTransTemplate
                .execute(new TransactionCallback<TaskStatus>() {
                    @Override
                    public TaskStatus doInTransaction(
                            TransactionStatus status) {
                        AssertUtils.notEmpty(taskId, "taskId is empty.");
                        AssertUtils.notNull(targetStatus,
                                "targetStatus is empty.");
                        
                        TaskStatus taskStatus = finalTaskStatusService
                                .findWithLockByTaskId(taskId);
                        //如果原状态不为空，则需要判断原状态是否和传入的一致(如果原状态为空，或，非空的原状 ==实际状态)
                        AssertUtils.isTrue(
                                sourceStatus == null || sourceStatus
                                        .equals(taskStatus.getStatus()),
                                "任务状态应为待执行.taskId:{} ; taskStatus.id:{} ; sourceStatus:{} ; taskStatus.status:{}",
                                new Object[] { taskStatus.getTaskId(),
                                        taskStatus.getId(), sourceStatus,
                                        taskStatus.getStatus() });
                        
                        BeanWrapper taskStatusBW = PropertyAccessorFactory
                                .forBeanPropertyAccess(taskStatus);
                        for (Entry<String, Object> entryTemp : finalStatusUpdateRowMap
                                .entrySet()) {
                            String key = entryTemp.getKey();
                            Object value = entryTemp.getValue();
                            if ("id".equals(key)) {
                                continue;
                            }
                            if (!taskStatusBW.isWritableProperty(key)) {
                                continue;
                            }
                            taskStatusBW.setPropertyValue(key, value);
                        }
                        taskStatus.setStatus(targetStatus);
                        taskStatus.setSignature(signature);
                        
                        finalTaskStatusService.updateById(taskStatus);
                        return taskStatus;
                    }
                    
                });
        return status;
    }
    
    /**
     * 记录任务执行日志<br/>
     * <功能详细描述>
     * @param taskDef
     * @param startDate
     * @param endDate
     * @param jobDataMap
     * @param result
     * @param signature [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    private void log(final TaskDef taskDef, final Date start, final Date end,
            final String taskStatusAttributes, final TaskResultEnum result,
            final String signature) {
        final TaskExecuteLogService finalTaskExecuteLogService = this.taskExecuteLogService;
        try {
            this.taskExecuteLogTransTemplate
                    .execute(new TransactionCallbackWithoutResult() {
                        
                        @Override
                        protected void doInTransactionWithoutResult(
                                TransactionStatus status) {
                            TaskExecuteLog log = new TaskExecuteLog();
                            
                            log.setTaskId(taskDef.getId());
                            log.setName(taskDef.getName());
                            log.setRemark(taskDef.getRemark());
                            
                            log.setParameters(taskStatusAttributes);
                            log.setStart(start);
                            log.setEnd(end);
                            log.setConsuming(end.getTime() - start.getTime());
                            
                            log.setResult(result);
                            log.setSignature(signature);
                            
                            finalTaskExecuteLogService.insert(log);
                        }
                    });
        } catch (TransactionException e) {
            logger.error("log error.errorMessage:" + e.getMessage(), e);
        }
    }
    
    /**
     * @param 对taskDefService进行赋值
     */
    public void setTaskDefService(TaskDefService taskDefService) {
        this.taskDefService = taskDefService;
    }
    
    /**
     * @param 对taskStatusService进行赋值
     */
    public void setTaskStatusService(TaskStatusService taskStatusService) {
        this.taskStatusService = taskStatusService;
    }
    
    /**
     * @param 对signature进行赋值
     */
    public void setSignature(String signature) {
        this.signature = signature;
    }
    
    /**
     * @param 对taskExecuteLogService进行赋值
     */
    public void setTaskExecuteLogService(
            TaskExecuteLogService taskExecuteLogService) {
        this.taskExecuteLogService = taskExecuteLogService;
    }
    
    /**
     * @param 对transactionManager进行赋值
     */
    public void setTransactionManager(
            PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    /**
     * 方法Key
     * 因MethodInvocation.getMethod\
     *     与BeanClass.getMethod中，可能取得的父类的方法的hashCode值不一致
     *    会导致拦截器中识别方法是否被拦截不一致
     * 
     * @author  PengQingyang
     * @version  [版本号, 2022年5月11日]
     * @see  [相关类/方法]
     * @since  [产品/模块版本]
     */
    static class MethodCacheKey {
        
        private String name;
        
        private Class<?>[] parameterTypes;
        
        /** <默认构造函数> */
        public MethodCacheKey(String name, Class<?>[] parameterTypes) {
            super();
            this.name = name;
            this.parameterTypes = parameterTypes;
        }
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }
        
        public void setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
        }
        
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
        
        @Override
        public boolean equals(Object obj) {
            return EqualsBuilder.reflectionEquals(this, obj);
        }
    }
    
    //public static void main(String[] args) {
    //    MethodCacheKey t = new MethodCacheKey("test",
    //            new Class<?>[] { String.class });
    //    MethodCacheKey t1 = new MethodCacheKey("test", new Class<?>[] {});
    //    MethodCacheKey t2 = new MethodCacheKey("test",
    //            new Class<?>[] { String.class, Date.class });
    //    MethodCacheKey t3 = new MethodCacheKey("test",
    //            new Class<?>[] { String.class, Date.class });
    //    
    //    System.out.println(t.hashCode());
    //    System.out.println(t1.hashCode());
    //    System.out.println(t2.hashCode());
    //    System.out.println(t3.hashCode());
    //}
}
